/**
 * Fetch-based HTTP utility with GET/POST and basic interceptors.
 */
import { useEnvStore, store } from '@/store'
import * as CircularJSON from 'circular-json'

export interface RequestOptions extends RequestInit {
  baseURL?: string
  params?: Record<string, string | number | boolean | undefined>
  timeout?: number // ms
  headers?: HeadersInit
}

type RequestInterceptor = (
  url: string,
  options: RequestOptions
) => Promise<{ url: string; options: RequestOptions }> | { url: string; options: RequestOptions }

type ResponseInterceptor = (response: Response) => Promise<Response> | Response

let requestInterceptor: RequestInterceptor | null = null
let responseInterceptor: ResponseInterceptor | null = null

export function setRequestInterceptor(fn: RequestInterceptor) {
  requestInterceptor = fn
}

export function setResponseInterceptor(fn: ResponseInterceptor) {
  responseInterceptor = fn
}

function buildQuery(params?: Record<string, string | number | boolean | undefined>) {
  if (!params) return ''
  const usp = new URLSearchParams()
  Object.entries(params).forEach(([key, value]) => {
    if (value === undefined || value === null) return
    usp.append(key, String(value))
  })
  const query = usp.toString()
  return query ? `?${query}` : ''
}

// 安全序列化，使用 circular-json 处理循环引用
function safeStringify(value: unknown): string {
  return CircularJSON.stringify(value as any)
}

async function fetchWithInterceptors(url: string, options: RequestOptions = {}) {
  // Read baseURL and timeout from Vite env
  const envBase = (import.meta.env?.VITE_APP_BASE_URL as string) ?? ''
  const baseURL = options.baseURL ?? envBase

  // Read env store values to inject into every request
  const env = useEnvStore(store)
  const token = env?.userInfo?.clientToken
  const placeId = env?.placeInfo?.placeId as any
  const clientId = env?.placeInfo?.clientId as any

  // Start from initial options and enforce placeId/clientId from env
  const initialOptions: RequestOptions = { ...options }
  initialOptions.params = { ...(initialOptions.params || {}) }
  if (placeId !== undefined && placeId !== null) initialOptions.params.placeId = placeId
  if (clientId !== undefined && clientId !== null) initialOptions.params.clientId = clientId

  let fullUrl = `${baseURL || ''}${url}`

  let finalOptions: RequestOptions = {
    ...initialOptions,
    headers: {
      Accept: 'application/json, text/plain, */*',
      ...(initialOptions.headers || {})
    }
  }

  // Inject token header
  if (token) {
    finalOptions.headers = {
      ...(finalOptions.headers || {}),
      Authorization: token
    }
  }

  // Allow custom request interceptor to modify url/options
  if (requestInterceptor) {
    const result = await requestInterceptor(fullUrl, finalOptions)
    fullUrl = result.url
    finalOptions = result.options
    // Re-enforce env-driven params and token after interceptor
    finalOptions.params = { ...(finalOptions.params || {}) }
    if (placeId !== undefined && placeId !== null) finalOptions.params.placeId = placeId
    if (clientId !== undefined && clientId !== null) finalOptions.params.clientId = clientId
    if (token) {
      finalOptions.headers = {
        ...(finalOptions.headers || {}),
        Authorization: `Bearer ${token}`
      }
    }
  }

  const query = buildQuery(finalOptions.params)
  fullUrl = `${fullUrl}${query}`

  const controller = new AbortController()
  finalOptions.signal = controller.signal
  const envTimeout = Number(import.meta.env?.VITE_SERVICE_TIME_OUT ?? 15000)
  const timeout = options.timeout ?? envTimeout
  const timer = setTimeout(() => controller.abort(), timeout)

  let response: Response
  try {
    response = await fetch(fullUrl, finalOptions)
  } finally {
    clearTimeout(timer)
  }

  if (responseInterceptor) {
    response = await responseInterceptor(response)
  }

  const contentType = response.headers.get('content-type') || ''
  const isJSON = contentType.includes('application/json')

  if (!response.ok) {
    const errorPayload = isJSON ? await safeJson(response) : await response.text()
    const error = new Error(`HTTP ${response.status}: ${response.statusText}`)
    ;(error as any).payload = errorPayload
    ;(error as any).status = response.status
    throw error
  }

  return isJSON ? await safeJson(response) : await response.text()
}

async function safeJson(resp: Response) {
  try {
    return await resp.json()
  } catch {
    return null
  }
}

export async function get<T = any>(url: string, options: RequestOptions = {}): Promise<T> {
  return await fetchWithInterceptors(url, {
    ...options,
    method: 'GET'
  })
}

export async function post<T = any>(url: string, data?: unknown, options: RequestOptions = {}): Promise<T> {
  const headers: HeadersInit = {
    'Content-Type': 'application/json',
    ...(options.headers || {})
  }
  return await fetchWithInterceptors(url, {
    ...options,
    method: 'POST',
    headers,
    body: data !== undefined ? safeStringify(data) : undefined
  })
}

/**
 * Example interceptors (optional):
 * setRequestInterceptor(async (url, options) => {
 *   // attach token, custom headers, etc.
 *   return { url, options: { ...options, headers: { ...(options.headers||{}), Authorization: `Bearer token` } } }
 * })
 *
 * setResponseInterceptor(async (resp) => {
 *   // global response handling, e.g., refresh token on 401
 *   return resp
 * })
 */